#include  "wx/wxprec.h"

#ifdef    __BORLANDC__
#pragma hdrstop
#endif  //__BORLANDC__

#if wxUSE_CONFIG && wxUSE_FILECONFIG

#ifndef   WX_PRECOMP
#include  "wx/dynarray.h"
#include  "wx/string.h"
#include  "wx/intl.h"
#include  "wx/log.h"
#include  "wx/app.h"
#include  "wx/utils.h"    // for wxGetHomeDir
#if wxUSE_STREAMS
#include  "wx/stream.h"
#endif // wxUSE_STREAMS
#endif  //WX_PRECOMP

#include  "wx/file.h"
#include  "wx/textfile.h"
#include  "wx/memtext.h"
#include  "wx/config.h"
#include  "wx/fileconf.h"
#include  "wx/filefn.h"

#if defined(__WXPM__)
#define INCL_DOS
#include <os2.h>
#endif

#include  <stdlib.h>
#include  <ctype.h>

#define CONST_CAST ((wxFileConfig *)this)->

#ifndef MAX_PATH
#define MAX_PATH 512
#endif

#define FILECONF_TRACE_MASK _T("fileconf")

static int LINKAGEMODE CompareEntries( wxFileConfigEntry *p1, wxFileConfigEntry *p2 );
static int LINKAGEMODE CompareGroups( wxFileConfigGroup *p1, wxFileConfigGroup *p2 );
static wxString FilterInValue( const wxString& str );
static wxString FilterOutValue( const wxString& str );
static wxString FilterInEntryName( const wxString& str );
static wxString FilterOutEntryName( const wxString& str );
static wxString GetAppName( const wxString& appname );

#ifdef WXMAKINGDLL_BASE
WX_DEFINE_SORTED_USER_EXPORTED_ARRAY( wxFileConfigEntry *, ArrayEntries);
WX_DEFINE_SORTED_USER_EXPORTED_ARRAY( wxFileConfigGroup *, ArrayGroups);
#else
WX_DEFINE_SORTED_ARRAY( wxFileConfigEntry *, ArrayEntries );
WX_DEFINE_SORTED_ARRAY( wxFileConfigGroup *, ArrayGroups );
#endif

class wxFileConfigLineList {
  public:
    void SetNext( wxFileConfigLineList *pNext )  { m_pNext = pNext; }
    void SetPrev( wxFileConfigLineList *pPrev )  { m_pPrev = pPrev; }

    // ctor
    wxFileConfigLineList( const wxString& str,
                          wxFileConfigLineList *pNext = NULL ) : m_strLine( str ) { SetNext( pNext ); SetPrev( NULL ); }

    // next/prev nodes in the linked list
    wxFileConfigLineList *Next() const { return m_pNext;  }
    wxFileConfigLineList *Prev() const { return m_pPrev;  }

    // get/change lines text
    void SetText( const wxString& str ) { m_strLine = str;  }
    const wxString& Text() const { return m_strLine; }

  private:
    wxString  m_strLine;                  // line contents
    wxFileConfigLineList *m_pNext,        // next node
                         *m_pPrev;        // previous one

    DECLARE_NO_COPY_CLASS( wxFileConfigLineList )
};

// ----------------------------------------------------------------------------
// wxFileConfigEntry: a name/value pair
// ----------------------------------------------------------------------------

class wxFileConfigEntry {
  private:
    wxFileConfigGroup *m_pParent; // group that contains us

    wxString      m_strName,      // entry name
                  m_strValue;     //       value
    bool          m_bImmutable: 1, // can be overriden locally?
                  m_bHasValue: 1; // set after first call to SetValue()

    int           m_nLine;        // used if m_pLine == NULL only

    // pointer to our line in the linked list or NULL if it was found in global
    // file (which we don't modify)
    wxFileConfigLineList *m_pLine;

  public:
    wxFileConfigEntry( wxFileConfigGroup *pParent,
                       const wxString& strName, int nLine );

    // simple accessors
    const wxString& Name()        const { return m_strName;    }
    const wxString& Value()       const { return m_strValue;   }
    wxFileConfigGroup *Group()    const { return m_pParent;    }
    bool            IsImmutable() const { return m_bImmutable; }
    bool            IsLocal()     const { return m_pLine != 0; }
    int             Line()        const { return m_nLine;      }
    wxFileConfigLineList *
    GetLine()     const { return m_pLine;      }

    // modify entry attributes
    void SetValue( const wxString& strValue, bool bUser = true );
    void SetLine( wxFileConfigLineList *pLine );

    DECLARE_NO_COPY_CLASS( wxFileConfigEntry )
};

// ----------------------------------------------------------------------------
// wxFileConfigGroup: container of entries and other groups
// ----------------------------------------------------------------------------

class wxFileConfigGroup {
  private:
    wxFileConfig *m_pConfig;          // config object we belong to
    wxFileConfigGroup  *m_pParent;    // parent group (NULL for root group)
    ArrayEntries  m_aEntries;         // entries in this group
    ArrayGroups   m_aSubgroups;       // subgroups
    wxString      m_strName;          // group's name
    wxFileConfigLineList *m_pLine;    // pointer to our line in the linked list
    wxFileConfigEntry *m_pLastEntry;  // last entry/subgroup of this group in the
    wxFileConfigGroup *m_pLastGroup;  // local file (we insert new ones after it)

    // DeleteSubgroupByName helper
    bool DeleteSubgroup( wxFileConfigGroup *pGroup );

    // used by Rename()
    void UpdateGroupAndSubgroupsLines();

  public:
    // ctor
    wxFileConfigGroup( wxFileConfigGroup *pParent, const wxString& strName, wxFileConfig * );

    // dtor deletes all entries and subgroups also
    ~wxFileConfigGroup();

    // simple accessors
    const wxString& Name()    const { return m_strName; }
    wxFileConfigGroup    *Parent()  const { return m_pParent; }
    wxFileConfig   *Config()  const { return m_pConfig; }

    const ArrayEntries& Entries() const { return m_aEntries;   }
    const ArrayGroups&  Groups()  const { return m_aSubgroups; }
    bool  IsEmpty() const { return Entries().IsEmpty() && Groups().IsEmpty(); }

    // find entry/subgroup (NULL if not found)
    wxFileConfigGroup *FindSubgroup( const wxChar *szName ) const;
    wxFileConfigEntry *FindEntry( const wxChar *szName ) const;

    // delete entry/subgroup, return false if doesn't exist
    bool DeleteSubgroupByName( const wxChar *szName );
    bool DeleteEntry( const wxChar *szName );

    // create new entry/subgroup returning pointer to newly created element
    wxFileConfigGroup *AddSubgroup( const wxString& strName );
    wxFileConfigEntry *AddEntry( const wxString& strName, int nLine = wxNOT_FOUND );

    void SetLine( wxFileConfigLineList *pLine );

    // rename: no checks are done to ensure that the name is unique!
    void Rename( const wxString& newName );

    //
    wxString GetFullName() const;

    // get the last line belonging to an entry/subgroup of this group
    wxFileConfigLineList *GetGroupLine();     // line which contains [group]
    // may be NULL for "/" only
    wxFileConfigLineList *GetLastEntryLine(); // after which our subgroups start
    wxFileConfigLineList *GetLastGroupLine(); // after which the next group starts

    // called by entries/subgroups when they're created/deleted
    void SetLastEntry( wxFileConfigEntry *pEntry );
    void SetLastGroup( wxFileConfigGroup *pGroup ) { m_pLastGroup = pGroup; }

    DECLARE_NO_COPY_CLASS( wxFileConfigGroup )
};

wxString wxFileConfig::GetGlobalDir() {
  return wxT( "/etc/" );
}

wxString wxFileConfig::GetLocalDir() {
  wxString strDir;
  wxGetHomeDir( &strDir );
  if(
    ( strDir.Last() != wxT( '/' ) )
  ) {
    strDir << wxT( '/' );
  }
  return strDir;
}

wxString wxFileConfig::GetGlobalFileName( const wxChar *szFile ) {
  wxString str = GetGlobalDir();
  str << szFile;
  if( wxStrchr( szFile, wxT( '.' ) ) == NULL ) {
    str << wxT( ".conf" );
  }
  return str;
}

wxString wxFileConfig::GetLocalFileName( const wxChar *szFile ) {
  wxString str = GetLocalDir();
  str << wxT( '.' );
  str << szFile;
  return str;
}

void wxFileConfig::Init() {
  m_pCurrentGroup =
    m_pRootGroup    = new wxFileConfigGroup( NULL, wxEmptyString, this );
  m_linesHead =
    m_linesTail = NULL;
  if( !m_strGlobalFile.empty() && wxFile::Exists( m_strGlobalFile ) ) {
    wxTextFile fileGlobal( m_strGlobalFile );
    if( fileGlobal.Open( *m_conv ) ) {
      Parse( fileGlobal, false );
      SetRootPath();
    } else {
      wxLogWarning( _( "can't open global configuration file '%s'." ), m_strGlobalFile.c_str() );
    }
  }
  if( !m_strLocalFile.empty() && wxFile::Exists( m_strLocalFile ) ) {
    wxTextFile fileLocal( m_strLocalFile );
    if( fileLocal.Open( *m_conv ) ) {
      Parse( fileLocal, true );
      SetRootPath();
    } else
    { wxLogWarning( _( "can't open user configuration file '%s'." ),  m_strLocalFile.c_str() ); }
  }
  m_isDirty = false;
}

wxFileConfig::wxFileConfig( const wxString& appName, const wxString& vendorName, const wxString& strLocal,
                            const wxString& strGlobal, long style, const wxMBConv& conv )
  : wxConfigBase( ::GetAppName( appName ), vendorName, strLocal, strGlobal, style ),
    m_strLocalFile( strLocal ), m_strGlobalFile( strGlobal ),
    m_conv( conv.Clone() ) {
  if( m_strLocalFile.empty() && ( style & wxCONFIG_USE_LOCAL_FILE ) ) {
    m_strLocalFile = GetLocalFileName( GetAppName() );
    if( style & wxCONFIG_USE_SUBDIR ) {
      m_strLocalFile << wxFILE_SEP_PATH << GetAppName() << _T( ".conf" );
    }
  }
  if( m_strGlobalFile.empty() && ( style & wxCONFIG_USE_GLOBAL_FILE ) ) {
    m_strGlobalFile = GetGlobalFileName( GetAppName() );
  }
  if( !m_strLocalFile.empty() ) {
    SetStyle( GetStyle() | wxCONFIG_USE_LOCAL_FILE );
  }
  if( !m_strGlobalFile.empty() ) {
    SetStyle( GetStyle() | wxCONFIG_USE_GLOBAL_FILE );
  }
  if( !( style & wxCONFIG_USE_RELATIVE_PATH ) ) {
    if( !m_strLocalFile.empty() && !wxIsAbsolutePath( m_strLocalFile ) ) {
      const wxString strLocalOrig = m_strLocalFile;
      m_strLocalFile = GetLocalDir();
      m_strLocalFile << strLocalOrig;
    }
    if( !m_strGlobalFile.empty() && !wxIsAbsolutePath( m_strGlobalFile ) ) {
      const wxString strGlobalOrig = m_strGlobalFile;
      m_strGlobalFile = GetGlobalDir();
      m_strGlobalFile << strGlobalOrig;
    }
  }
  SetUmask( -1 );
  Init();
}

#if wxUSE_STREAMS

wxFileConfig::wxFileConfig( wxInputStream &inStream, const wxMBConv& conv )
  : m_conv( conv.Clone() ) {
  SetStyle( GetStyle() | wxCONFIG_USE_LOCAL_FILE );
  m_pCurrentGroup =
    m_pRootGroup    = new wxFileConfigGroup( NULL, wxEmptyString, this );
  m_linesHead =
    m_linesTail = NULL;
  wxString str;
  {
    static const size_t chunkLen = 1024;
    wxMemoryBuffer buf( chunkLen );
    do {
      inStream.Read( buf.GetAppendBuf( chunkLen ), chunkLen );
      buf.UngetAppendBuf( inStream.LastRead() );
      const wxStreamError err = inStream.GetLastError();
      if( err != wxSTREAM_NO_ERROR && err != wxSTREAM_EOF ) {
        wxLogError( _( "Error reading config options." ) );
        break;
      }
    } while( !inStream.Eof() );
    size_t len;
    str = conv.cMB2WC( ( char * )buf.GetData(), buf.GetDataLen(), &len );
    if( !len && buf.GetDataLen() ) {
      wxLogError( _( "Failed to read config options." ) );
    }
  }
  str = wxTextBuffer::Translate( str );
  wxMemoryText memText;
  const wxChar *pEOL = wxTextBuffer::GetEOL( wxTextBuffer::typeDefault );
  const size_t EOLLen = wxStrlen( pEOL );
  int posLineStart = str.Find( pEOL );
  while( posLineStart != -1 ) {
    wxString line( str.Left( posLineStart ) );
    memText.AddLine( line );
    str = str.Mid( posLineStart + EOLLen );
    posLineStart = str.Find( pEOL );
  }
  if( !str.empty() ) {
    memText.AddLine( str );
  }
  Parse( memText, true );
  SetRootPath();
  ResetDirty();
}

#endif // wxUSE_STREAMS

void wxFileConfig::CleanUp() {
  delete m_pRootGroup;
  wxFileConfigLineList *pCur = m_linesHead;
  while( pCur != NULL ) {
    wxFileConfigLineList *pNext = pCur->Next();
    delete pCur;
    pCur = pNext;
  }
}

wxFileConfig::~wxFileConfig() {
  Flush();
  CleanUp();
  delete m_conv;
}

void wxFileConfig::Parse( const wxTextBuffer& buffer, bool bLocal ) {
  const wxChar *pStart;
  const wxChar *pEnd;
  wxString strLine;
  size_t nLineCount = buffer.GetLineCount();
  for( size_t n = 0; n < nLineCount; n++ ) {
    strLine = buffer[n];
    if( bLocal ) {
      LineListAppend( strLine );
    }
    for( pStart = strLine; wxIsspace( *pStart ); pStart++ );
    if( *pStart == wxT( '\0' ) || *pStart == wxT( ';' ) || *pStart == wxT( '#' ) ) {
      continue;
    }
    if( *pStart == wxT( '[' ) ) {         // a new group
      pEnd = pStart;
      while( *++pEnd != wxT( ']' ) ) {
        if( *pEnd == wxT( '\\' ) ) {
          // the next char is escaped, so skip it even if it is ']'
          pEnd++;
        }
        if( *pEnd == wxT( '\n' ) || *pEnd == wxT( '\0' ) ) {
          // we reached the end of line, break out of the loop
          break;
        }
      }
      if( *pEnd != wxT( ']' ) ) {
        wxLogError( _( "file '%s': unexpected character %c at line %d." ), buffer.GetName(), *pEnd, n + 1 );
        continue; // skip this line
      }
      // group name here is always considered as abs path
      wxString strGroup;
      pStart++;
      strGroup << wxCONFIG_PATH_SEPARATOR
               << FilterInEntryName( wxString( pStart, pEnd - pStart ) );
      SetPath( strGroup );
      if( bLocal ) {
        if( m_pCurrentGroup->Parent() ) {
          m_pCurrentGroup->Parent()->SetLastGroup( m_pCurrentGroup );
        }
        m_pCurrentGroup->SetLine( m_linesTail );
      }
      bool bCont = true;
      while( *++pEnd != wxT( '\0' ) && bCont ) {
        switch( *pEnd ) {
          case wxT( '#' ):
          case wxT( ';' ):
            bCont = false;
            break;
          case wxT( ' ' ):
          case wxT( '\t' ):
            break;
          default:
            wxLogWarning( _( "file '%s', line %d: '%s' ignored after group header." ),
                          buffer.GetName(), n + 1, pEnd );
            bCont = false;
        }
      }
    } else {
      pEnd = pStart;
      while( *pEnd && *pEnd != wxT( '=' ) ) {
        if( *pEnd == wxT( '\\' ) ) {
          pEnd++;
          if( !*pEnd ) {
            break;
          }
        }
        pEnd++;
      }
      wxString strKey( FilterInEntryName( wxString( pStart, pEnd ).Trim() ) );
      while( wxIsspace( *pEnd ) ) {
        pEnd++;
      }
      if( *pEnd++ != wxT( '=' ) ) {
        wxLogError( _( "file '%s', line %d: '=' expected." ),
                    buffer.GetName(), n + 1 );
      } else {
        wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry( strKey );
        if( pEntry == NULL ) {
          // new entry
          pEntry = m_pCurrentGroup->AddEntry( strKey, n );
        } else {
          if( bLocal && pEntry->IsImmutable() ) {
            // immutable keys can't be changed by user
            wxLogWarning( _( "file '%s', line %d: value for immutable key '%s' ignored." ),
                          buffer.GetName(), n + 1, strKey.c_str() );
            continue;
          } else if( !bLocal || pEntry->IsLocal() ) {
            wxLogWarning( _( "file '%s', line %d: key '%s' was first found at line %d." ),
                          buffer.GetName(), n + 1, strKey.c_str(), pEntry->Line() );
          }
        }
        if( bLocal ) {
          pEntry->SetLine( m_linesTail );
        }
        // skip whitespace
        while( wxIsspace( *pEnd ) ) {
          pEnd++;
        }
        wxString value = pEnd;
        if( !( GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS ) ) {
          value = FilterInValue( value );
        }
        pEntry->SetValue( value, false );
      }
    }
  }
}

void wxFileConfig::SetRootPath() {
  m_strPath.Empty();
  m_pCurrentGroup = m_pRootGroup;
}

bool
wxFileConfig::DoSetPath( const wxString& strPath, bool createMissingComponents ) {
  wxArrayString aParts;
  if( strPath.empty() ) {
    SetRootPath();
    return true;
  }
  if( strPath[0] == wxCONFIG_PATH_SEPARATOR ) {
    wxSplitPath( aParts, strPath );
  } else {
    wxString strFullPath = m_strPath;
    strFullPath << wxCONFIG_PATH_SEPARATOR << strPath;
    wxSplitPath( aParts, strFullPath );
  }
  size_t n;
  m_pCurrentGroup = m_pRootGroup;
  for( n = 0; n < aParts.Count(); n++ ) {
    wxFileConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup( aParts[n] );
    if( pNextGroup == NULL ) {
      if( !createMissingComponents ) {
        return false;
      }
      pNextGroup = m_pCurrentGroup->AddSubgroup( aParts[n] );
    }
    m_pCurrentGroup = pNextGroup;
  }
  m_strPath.Empty();
  for( n = 0; n < aParts.Count(); n++ ) {
    m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n];
  }
  return true;
}

void wxFileConfig::SetPath( const wxString& strPath ) {
  DoSetPath( strPath, true /* create missing path components */ );
}

bool wxFileConfig::GetFirstGroup( wxString& str, long& lIndex ) const {
  lIndex = 0;
  return GetNextGroup( str, lIndex );
}

bool wxFileConfig::GetNextGroup( wxString& str, long& lIndex ) const {
  if( size_t( lIndex ) < m_pCurrentGroup->Groups().Count() ) {
    str = m_pCurrentGroup->Groups()[( size_t )lIndex++]->Name();
    return true;
  } else {
    return false;
  }
}

bool wxFileConfig::GetFirstEntry( wxString& str, long& lIndex ) const {
  lIndex = 0;
  return GetNextEntry( str, lIndex );
}

bool wxFileConfig::GetNextEntry( wxString& str, long& lIndex ) const {
  if( size_t( lIndex ) < m_pCurrentGroup->Entries().Count() ) {
    str = m_pCurrentGroup->Entries()[( size_t )lIndex++]->Name();
    return true;
  } else {
    return false;
  }
}

size_t wxFileConfig::GetNumberOfEntries( bool bRecursive ) const {
  size_t n = m_pCurrentGroup->Entries().Count();
  if( bRecursive ) {
    wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
    size_t nSubgroups = m_pCurrentGroup->Groups().Count();
    for( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
      CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
      n += GetNumberOfEntries( true );
      CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
    }
  }
  return n;
}

size_t wxFileConfig::GetNumberOfGroups( bool bRecursive ) const {
  size_t n = m_pCurrentGroup->Groups().Count();
  if( bRecursive ) {
    wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
    size_t nSubgroups = m_pCurrentGroup->Groups().Count();
    for( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
      CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
      n += GetNumberOfGroups( true );
      CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
    }
  }
  return n;
}

bool wxFileConfig::HasGroup( const wxString& strName ) const {
  if( strName.empty() ) {
    return false;
  }
  const wxString pathOld = GetPath();
  wxFileConfig *self = wx_const_cast( wxFileConfig *, this );
  const bool
  rc = self->DoSetPath( strName, false /* don't create missing components */ );
  self->SetPath( pathOld );
  return rc;
}

bool wxFileConfig::HasEntry( const wxString& entry ) const {
  wxString path = entry.BeforeLast( wxCONFIG_PATH_SEPARATOR );
  if( path.empty() && *entry.c_str() == wxCONFIG_PATH_SEPARATOR ) {
    path = wxCONFIG_PATH_SEPARATOR;
  }
  wxString pathOld;
  wxFileConfig * const self = wx_const_cast( wxFileConfig *, this );
  if( !path.empty() ) {
    pathOld = GetPath();
    if( pathOld.empty() ) {
      pathOld = wxCONFIG_PATH_SEPARATOR;
    }
    if( !self->DoSetPath( path, false ) ) {
      return false;
    }
  }
  const bool exists = m_pCurrentGroup->FindEntry( entry.AfterLast( wxCONFIG_PATH_SEPARATOR ) ) != NULL;
  if( !pathOld.empty() ) {
    self->SetPath( pathOld );
  }
  return exists;
}

bool wxFileConfig::DoReadString( const wxString& key, wxString* pStr ) const {
  wxConfigPathChanger path( this, key );
  wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry( path.Name() );
  if( pEntry == NULL ) {
    return false;
  }
  *pStr = pEntry->Value();
  return true;
}

bool wxFileConfig::DoReadLong( const wxString& key, long *pl ) const {
  wxString str;
  if( !Read( key, &str ) ) {
    return false;
  }
  str.Trim();
  return str.ToLong( pl );
}

bool wxFileConfig::DoWriteString( const wxString& key, const wxString& szValue ) {
  wxConfigPathChanger path( this, key );
  wxString strName = path.Name();
  wxLogTrace( FILECONF_TRACE_MASK, _T( "  Writing String '%s' = '%s' to Group '%s'" ), strName.c_str(),
              szValue.c_str(), GetPath().c_str() );
  if( strName.empty() ) {
    wxASSERT_MSG( szValue.empty(), wxT( "can't set value of a group!" ) );
    wxLogTrace( FILECONF_TRACE_MASK, _T( "  Creating group %s" ), m_pCurrentGroup->Name().c_str() );
    SetDirty();
    ( void )m_pCurrentGroup->GetGroupLine();
  } else {
    if( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX ) {
      wxLogError( _( "Config entry name cannot start with '%c'." ), wxCONFIG_IMMUTABLE_PREFIX );
      return false;
    }
    wxFileConfigEntry   *pEntry = m_pCurrentGroup->FindEntry( strName );
    if( pEntry == 0 ) {
      wxLogTrace( FILECONF_TRACE_MASK, _T( "  Adding Entry %s" ), strName.c_str() );
      pEntry = m_pCurrentGroup->AddEntry( strName );
    }
    wxLogTrace( FILECONF_TRACE_MASK, _T( "  Setting value %s" ), szValue.c_str() );
    pEntry->SetValue( szValue );
    SetDirty();
  }
  return true;
}

bool wxFileConfig::DoWriteLong( const wxString& key, long lValue ) {
  return Write( key, wxString::Format( _T( "%ld" ), lValue ) );
}

bool wxFileConfig::Flush( bool ) {
  if( !IsDirty() || !m_strLocalFile ) {
    return true;
  }
  wxCHANGE_UMASK( m_umask );
  wxTempFile file( m_strLocalFile );
  if( !file.IsOpened() ) {
    wxLogError( _( "can't open user configuration file." ) );
    return false;
  }
  wxString filetext;
  filetext.reserve( 4096 );
  for( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() ) {
    filetext << p->Text() << wxTextFile::GetEOL();
  }
  if( !file.Write( filetext, *m_conv ) ) {
    wxLogError( _( "can't write user configuration file." ) );
    return false;
  }
  if( !file.Commit() ) {
    wxLogError( _( "Failed to update user configuration file." ) );
    return false;
  }
  ResetDirty();
  return true;
}

#if wxUSE_STREAMS

bool wxFileConfig::Save( wxOutputStream& os, const wxMBConv& conv ) {
  for( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() ) {
    wxString line = p->Text();
    line += wxTextFile::GetEOL();
    wxCharBuffer buf( line.mb_str( conv ) );
    if( !os.Write( buf, strlen( buf ) ) ) {
      wxLogError( _( "Error saving user configuration data." ) );
      return false;
    }
  }
  ResetDirty();
  return true;
}

#endif // wxUSE_STREAMS

bool wxFileConfig::RenameEntry( const wxString& oldName, const wxString& newName ) {
  wxASSERT_MSG( !wxStrchr( oldName, wxCONFIG_PATH_SEPARATOR ), _T( "RenameEntry(): paths are not supported" ) );
  wxFileConfigEntry *oldEntry = m_pCurrentGroup->FindEntry( oldName );
  if( !oldEntry ) {
    return false;
  }
  if( m_pCurrentGroup->FindEntry( newName ) ) {
    return false;
  }
  wxString value = oldEntry->Value();
  if( !m_pCurrentGroup->DeleteEntry( oldName ) ) {
    return false;
  }
  SetDirty();
  wxFileConfigEntry *newEntry = m_pCurrentGroup->AddEntry( newName );
  newEntry->SetValue( value );
  return true;
}

bool wxFileConfig::RenameGroup( const wxString& oldName, const wxString& newName ) {
  wxFileConfigGroup *group = m_pCurrentGroup->FindSubgroup( oldName );
  if( !group ) {
    return false;
  }
  if( m_pCurrentGroup->FindSubgroup( newName ) ) {
    return false;
  }
  group->Rename( newName );
  SetDirty();
  return true;
}

bool wxFileConfig::DeleteEntry( const wxString& key, bool bGroupIfEmptyAlso ) {
  wxConfigPathChanger path( this, key );
  if( !m_pCurrentGroup->DeleteEntry( path.Name() ) ) {
    return false;
  }
  SetDirty();
  if( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
    if( m_pCurrentGroup != m_pRootGroup ) {
      wxFileConfigGroup *pGroup = m_pCurrentGroup;
      SetPath( wxT( ".." ) );
      m_pCurrentGroup->DeleteSubgroupByName( pGroup->Name() );
    }
  }
  return true;
}

bool wxFileConfig::DeleteGroup( const wxString& key ) {
  wxConfigPathChanger path( this, RemoveTrailingSeparator( key ) );
  if( !m_pCurrentGroup->DeleteSubgroupByName( path.Name() ) ) {
    return false;
  }
  path.UpdateIfDeleted();
  SetDirty();
  return true;
}

bool wxFileConfig::DeleteAll() {
  CleanUp();
  if( !m_strLocalFile.empty() ) {
    if( wxFile::Exists( m_strLocalFile ) && wxRemove( m_strLocalFile ) == -1 ) {
      wxLogSysError( _( "can't delete user configuration file '%s'" ),
                     m_strLocalFile.c_str() );
      return false;
    }
  }
  Init();
  return true;
}

wxFileConfigLineList *wxFileConfig::LineListAppend( const wxString& str ) {
  wxLogTrace( FILECONF_TRACE_MASK, _T( "    ** Adding Line '%s'" ), str.c_str() );
  wxLogTrace( FILECONF_TRACE_MASK, _T( "        head: %s" ),
              ( ( m_linesHead ) ? m_linesHead->Text().c_str() : wxEmptyString ) );
  wxLogTrace( FILECONF_TRACE_MASK, _T( "        tail: %s" ),
              ( ( m_linesTail ) ? m_linesTail->Text().c_str() : wxEmptyString ) );
  wxFileConfigLineList *pLine = new wxFileConfigLineList( str );
  if( m_linesTail == NULL ) {
    m_linesHead = pLine;
  } else {
    m_linesTail->SetNext( pLine );
    pLine->SetPrev( m_linesTail );
  }
  m_linesTail = pLine;
  wxLogTrace( FILECONF_TRACE_MASK, _T( "        head: %s" ),
              ( ( m_linesHead ) ? m_linesHead->Text().c_str() : wxEmptyString ) );
  wxLogTrace( FILECONF_TRACE_MASK, _T( "        tail: %s" ),
              ( ( m_linesTail ) ? m_linesTail->Text().c_str() : wxEmptyString ) );
  return m_linesTail;
}

wxFileConfigLineList *wxFileConfig::LineListInsert( const wxString& str,
    wxFileConfigLineList *pLine ) {
  wxLogTrace( FILECONF_TRACE_MASK, _T( "    ** Inserting Line '%s' after '%s'" ),
              str.c_str(),
              ( ( pLine ) ? pLine->Text().c_str() : wxEmptyString ) );
  wxLogTrace( FILECONF_TRACE_MASK, _T( "        head: %s" ),
              ( ( m_linesHead ) ? m_linesHead->Text().c_str() : wxEmptyString ) );
  wxLogTrace( FILECONF_TRACE_MASK, _T( "        tail: %s" ),
              ( ( m_linesTail ) ? m_linesTail->Text().c_str() : wxEmptyString ) );
  if( pLine == m_linesTail ) {
    return LineListAppend( str );
  }
  wxFileConfigLineList *pNewLine = new wxFileConfigLineList( str );
  if( pLine == NULL ) {
    pNewLine->SetNext( m_linesHead );
    m_linesHead->SetPrev( pNewLine );
    m_linesHead = pNewLine;
  } else {
    wxFileConfigLineList *pNext = pLine->Next();
    pNewLine->SetNext( pNext );
    pNewLine->SetPrev( pLine );
    pNext->SetPrev( pNewLine );
    pLine->SetNext( pNewLine );
  }
  wxLogTrace( FILECONF_TRACE_MASK, _T( "        head: %s" ),
              ( ( m_linesHead ) ? m_linesHead->Text().c_str() : wxEmptyString ) );
  wxLogTrace( FILECONF_TRACE_MASK, _T( "        tail: %s" ),
              ( ( m_linesTail ) ? m_linesTail->Text().c_str() : wxEmptyString ) );
  return pNewLine;
}

void wxFileConfig::LineListRemove( wxFileConfigLineList *pLine ) {
  wxLogTrace( FILECONF_TRACE_MASK, _T( "    ** Removing Line '%s'" ), pLine->Text().c_str() );
  wxLogTrace( FILECONF_TRACE_MASK, _T( "        head: %s" ),
              ( ( m_linesHead ) ? m_linesHead->Text().c_str() : wxEmptyString ) );
  wxLogTrace( FILECONF_TRACE_MASK, _T( "        tail: %s" ),
              ( ( m_linesTail ) ? m_linesTail->Text().c_str() : wxEmptyString ) );
  wxFileConfigLineList    *pPrev = pLine->Prev(), *pNext = pLine->Next();
  if( pPrev == NULL ) {
    m_linesHead = pNext;
  } else {
    pPrev->SetNext( pNext );
  }
  if( pNext == NULL ) {
    m_linesTail = pPrev;
  } else
  { pNext->SetPrev( pPrev ); }
  if( m_pRootGroup->GetGroupLine() == pLine ) {
    m_pRootGroup->SetLine( m_linesHead );
  }
  wxLogTrace( FILECONF_TRACE_MASK,
              _T( "        head: %s" ),
              ( ( m_linesHead ) ? m_linesHead->Text().c_str() : wxEmptyString ) );
  wxLogTrace( FILECONF_TRACE_MASK,
              _T( "        tail: %s" ),
              ( ( m_linesTail ) ? m_linesTail->Text().c_str() : wxEmptyString ) );
  delete pLine;
}

bool wxFileConfig::LineListIsEmpty() {
  return m_linesHead == NULL;
}

wxFileConfigGroup::wxFileConfigGroup( wxFileConfigGroup *pParent, const wxString& strName, wxFileConfig *pConfig )
  : m_aEntries( CompareEntries ), m_aSubgroups( CompareGroups ), m_strName( strName ) {
  m_pConfig = pConfig;
  m_pParent = pParent;
  m_pLine   = NULL;
  m_pLastEntry = NULL;
  m_pLastGroup = NULL;
}

wxFileConfigGroup::~wxFileConfigGroup() {
  size_t n, nCount = m_aEntries.Count();
  for( n = 0; n < nCount; n++ ) {
    delete m_aEntries[n];
  }
  nCount = m_aSubgroups.Count();
  for( n = 0; n < nCount; n++ ) {
    delete m_aSubgroups[n];
  }
}

void wxFileConfigGroup::SetLine( wxFileConfigLineList *pLine ) {
  wxASSERT_MSG( !m_pParent || !m_pLine || !pLine,
                _T( "changing line for a non-root group?" ) );
  m_pLine = pLine;
}

wxFileConfigLineList *wxFileConfigGroup::GetGroupLine() {
  wxLogTrace( FILECONF_TRACE_MASK, _T( "  GetGroupLine() for Group '%s'" ), Name().c_str() );
  if( !m_pLine ) {
    wxLogTrace( FILECONF_TRACE_MASK, _T( "    Getting Line item pointer" ) );
    wxFileConfigGroup   *pParent = Parent();
    if( pParent ) {
      wxLogTrace( FILECONF_TRACE_MASK, _T( "    checking parent '%s'" ), pParent->Name().c_str() );
      wxString    strFullName;
      strFullName << wxT( "[" ) << FilterOutEntryName( GetFullName().c_str() + 1 ) << wxT( "]" );
      m_pLine = m_pConfig->LineListInsert( strFullName, pParent->GetLastGroupLine() );
      pParent->SetLastGroup( this ); // we're surely after all the others
    }
  }
  return m_pLine;
}

wxFileConfigLineList *wxFileConfigGroup::GetLastGroupLine() {
  if( m_pLastGroup ) {
    wxFileConfigLineList *pLine = m_pLastGroup->GetLastGroupLine();
    wxASSERT_MSG( pLine, _T( "last group must have !NULL associated line" ) );
    return pLine;
  }
  return GetLastEntryLine();
}

wxFileConfigLineList *wxFileConfigGroup::GetLastEntryLine() {
  wxLogTrace( FILECONF_TRACE_MASK, _T( "  GetLastEntryLine() for Group '%s'" ),
              Name().c_str() );
  if( m_pLastEntry ) {
    wxFileConfigLineList    *pLine = m_pLastEntry->GetLine();
    wxASSERT_MSG( pLine, _T( "last entry must have !NULL associated line" ) );
    return pLine;
  }
  return GetGroupLine();
}

void wxFileConfigGroup::SetLastEntry( wxFileConfigEntry *pEntry ) {
  m_pLastEntry = pEntry;
  if( !m_pLine ) {
    wxASSERT_MSG( !m_pParent, _T( "unexpected for non root group" ) );
    m_pLine = pEntry->GetLine();
  }
}

void wxFileConfigGroup::UpdateGroupAndSubgroupsLines() {
  wxFileConfigLineList *line = GetGroupLine();
  wxCHECK_RET( line, _T( "a non root group must have a corresponding line!" ) );
  line->SetText( wxString::Format( _T( "[%s]" ), GetFullName().c_str() + 1 ) );
  const size_t nCount = m_aSubgroups.Count();
  for( size_t n = 0; n < nCount; n++ ) {
    m_aSubgroups[n]->UpdateGroupAndSubgroupsLines();
  }
}

void wxFileConfigGroup::Rename( const wxString& newName ) {
  wxCHECK_RET( m_pParent, _T( "the root group can't be renamed" ) );
  if( newName == m_strName ) {
    return;
  }
  m_pParent->m_aSubgroups.Remove( this );
  m_strName = newName;
  m_pParent->m_aSubgroups.Add( this );
  UpdateGroupAndSubgroupsLines();
}

wxString wxFileConfigGroup::GetFullName() const {
  wxString fullname;
  if( Parent() ) {
    fullname = Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
  }
  return fullname;
}

wxFileConfigEntry *wxFileConfigGroup::FindEntry( const wxChar *szName ) const {
  size_t i,
         lo = 0,
         hi = m_aEntries.Count();
  int res;
  wxFileConfigEntry *pEntry;
  while( lo < hi ) {
    i = ( lo + hi ) / 2;
    pEntry = m_aEntries[i];
    #if wxCONFIG_CASE_SENSITIVE
    res = wxStrcmp( pEntry->Name(), szName );
    #else
    res = wxStricmp( pEntry->Name(), szName );
    #endif
    if( res > 0 ) {
      hi = i;
    } else if( res < 0 ) {
      lo = i + 1;
    } else {
      return pEntry;
    }
  }
  return NULL;
}

wxFileConfigGroup *wxFileConfigGroup::FindSubgroup( const wxChar *szName ) const {
  size_t i,
         lo = 0,
         hi = m_aSubgroups.Count();
  int res;
  wxFileConfigGroup *pGroup;
  while( lo < hi ) {
    i = ( lo + hi ) / 2;
    pGroup = m_aSubgroups[i];
    #if wxCONFIG_CASE_SENSITIVE
    res = wxStrcmp( pGroup->Name(), szName );
    #else
    res = wxStricmp( pGroup->Name(), szName );
    #endif
    if( res > 0 ) {
      hi = i;
    } else if( res < 0 ) {
      lo = i + 1;
    } else
    { return pGroup; }
  }
  return NULL;
}

wxFileConfigEntry *wxFileConfigGroup::AddEntry( const wxString& strName, int nLine ) {
  wxASSERT( FindEntry( strName ) == 0 );
  wxFileConfigEntry   *pEntry = new wxFileConfigEntry( this, strName, nLine );
  m_aEntries.Add( pEntry );
  return pEntry;
}

wxFileConfigGroup *wxFileConfigGroup::AddSubgroup( const wxString& strName ) {
  wxASSERT( FindSubgroup( strName ) == 0 );
  wxFileConfigGroup   *pGroup = new wxFileConfigGroup( this, strName, m_pConfig );
  m_aSubgroups.Add( pGroup );
  return pGroup;
}

bool wxFileConfigGroup::DeleteSubgroupByName( const wxChar *szName ) {
  wxFileConfigGroup * const pGroup = FindSubgroup( szName );
  return pGroup ? DeleteSubgroup( pGroup ) : false;
}

bool wxFileConfigGroup::DeleteSubgroup( wxFileConfigGroup *pGroup ) {
  wxCHECK_MSG( pGroup, false, _T( "deleting non existing group?" ) );
  wxLogTrace( FILECONF_TRACE_MASK, _T( "Deleting group '%s' from '%s'" ), pGroup->Name().c_str(), Name().c_str() );
  wxLogTrace( FILECONF_TRACE_MASK, _T( "  (m_pLine) = prev: %p, this %p, next %p" ),
              m_pLine ? wx_static_cast( void*, m_pLine->Prev() ) : 0, wx_static_cast( void*, m_pLine ),
              m_pLine ? wx_static_cast( void*, m_pLine->Next() ) : 0 );
  wxLogTrace( FILECONF_TRACE_MASK, _T( "  text: '%s'" ), m_pLine ? m_pLine->Text().c_str() : wxEmptyString );
  size_t nCount = pGroup->m_aEntries.Count();
  wxLogTrace( FILECONF_TRACE_MASK, _T( "Removing %lu entries" ), ( unsigned long )nCount );
  for( size_t nEntry = 0; nEntry < nCount; nEntry++ ) {
    wxFileConfigLineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
    if( pLine ) {
      wxLogTrace( FILECONF_TRACE_MASK, _T( "    '%s'" ), pLine->Text().c_str() );
      m_pConfig->LineListRemove( pLine );
    }
  }
  nCount = pGroup->m_aSubgroups.Count();
  wxLogTrace( FILECONF_TRACE_MASK, _T( "Removing %lu subgroups" ), ( unsigned long )nCount );
  for( size_t nGroup = 0; nGroup < nCount; nGroup++ ) {
    pGroup->DeleteSubgroup( pGroup->m_aSubgroups[0] );
  }
  wxFileConfigLineList *pLine = pGroup->m_pLine;
  if( pLine ) {
    wxLogTrace( FILECONF_TRACE_MASK, _T( "  Removing line for group '%s' : '%s'" ), pGroup->Name().c_str(),
                pLine->Text().c_str() );
    wxLogTrace( FILECONF_TRACE_MASK, _T( "  Removing from group '%s' : '%s'" ), Name().c_str(),
                ( ( m_pLine ) ? m_pLine->Text().c_str() : wxEmptyString ) );
    if( pGroup == m_pLastGroup ) {
      wxLogTrace( FILECONF_TRACE_MASK, _T( "  Removing last group" ) );
      const size_t nSubgroups = m_aSubgroups.Count();
      m_pLastGroup = NULL;
      for( wxFileConfigLineList *pl = pLine->Prev(); pl && !m_pLastGroup; pl = pl->Prev() ) {
        for( size_t n = 0; n < nSubgroups; n++ ) {
          if( m_aSubgroups[n]->m_pLine == pl ) {
            m_pLastGroup = m_aSubgroups[n];
            break;
          }
        }
        if( pl == m_pLine ) {
          break;
        }
      }
    }
    m_pConfig->LineListRemove( pLine );
  } else {
    wxLogTrace( FILECONF_TRACE_MASK, _T( "  No line entry for Group '%s'?" ), pGroup->Name().c_str() );
  }
  m_aSubgroups.Remove( pGroup );
  delete pGroup;
  return true;
}

bool wxFileConfigGroup::DeleteEntry( const wxChar *szName ) {
  wxFileConfigEntry *pEntry = FindEntry( szName );
  if( !pEntry ) {
    return false;
  }
  wxFileConfigLineList *pLine = pEntry->GetLine();
  if( pLine != NULL ) {
    if( pEntry == m_pLastEntry ) {
      wxASSERT( m_pLine != NULL );
      wxFileConfigEntry *pNewLast = NULL;
      const wxFileConfigLineList * const
      pNewLastLine = m_pLastEntry->GetLine()->Prev();
      const size_t nEntries = m_aEntries.GetCount();
      for( size_t n = 0; n < nEntries; n++ ) {
        if( m_aEntries[n]->GetLine() == pNewLastLine ) {
          pNewLast = m_aEntries[n];
          break;
        }
      }
      m_pLastEntry = pNewLast;
    }
    m_pConfig->LineListRemove( pLine );
  }
  m_aEntries.Remove( pEntry );
  delete pEntry;
  return true;
}

wxFileConfigEntry::wxFileConfigEntry( wxFileConfigGroup *pParent, const wxString& strName, int nLine )
  : m_strName( strName ) {
  wxASSERT( !strName.empty() );
  m_pParent = pParent;
  m_nLine   = nLine;
  m_pLine   = NULL;
  m_bHasValue = false;
  m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
  if( m_bImmutable ) {
    m_strName.erase( 0, 1 );
  }
}

void wxFileConfigEntry::SetLine( wxFileConfigLineList *pLine ) {
  if( m_pLine != NULL ) {
    wxLogWarning( _( "entry '%s' appears more than once in group '%s'" ),
                  Name().c_str(), m_pParent->GetFullName().c_str() );
  }
  m_pLine = pLine;
  Group()->SetLastEntry( this );
}

void wxFileConfigEntry::SetValue( const wxString& strValue, bool bUser ) {
  if( bUser && IsImmutable() ) {
    wxLogWarning( _( "attempt to change immutable key '%s' ignored." ),Name().c_str() );
    return;
  }
  if( m_bHasValue && strValue == m_strValue ) {
    return;
  }
  m_bHasValue = true;
  m_strValue = strValue;
  if( bUser ) {
    wxString strValFiltered;
    if( Group()->Config()->GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS ) {
      strValFiltered = strValue;
    } else
    { strValFiltered = FilterOutValue( strValue ); }
    wxString    strLine;
    strLine << FilterOutEntryName( m_strName ) << wxT( '=' ) << strValFiltered;
    if( m_pLine ) {
      m_pLine->SetText( strLine );
    } else {
      wxFileConfigLineList *line = Group()->GetLastEntryLine();
      m_pLine = Group()->Config()->LineListInsert( strLine, line );
      Group()->SetLastEntry( this );
    }
  }
}

int CompareEntries( wxFileConfigEntry *p1, wxFileConfigEntry *p2 ) {
  #if wxCONFIG_CASE_SENSITIVE
  return wxStrcmp( p1->Name(), p2->Name() );
  #else
  return wxStricmp( p1->Name(), p2->Name() );
  #endif
}

int CompareGroups( wxFileConfigGroup *p1, wxFileConfigGroup *p2 ) {
  #if wxCONFIG_CASE_SENSITIVE
  return wxStrcmp( p1->Name(), p2->Name() );
  #else
  return wxStricmp( p1->Name(), p2->Name() );
  #endif
}

static wxString FilterInValue( const wxString& str ) {
  wxString strResult;
  strResult.Alloc( str.Len() );
  bool bQuoted = !str.empty() && str[0] == '"';
  for( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
    if( str[n] == wxT( '\\' ) ) {
      switch( str[++n] ) {
        case wxT( 'n' ):
          strResult += wxT( '\n' );
          break;
        case wxT( 'r' ):
          strResult += wxT( '\r' );
          break;
        case wxT( 't' ):
          strResult += wxT( '\t' );
          break;
        case wxT( '\\' ):
          strResult += wxT( '\\' );
          break;
        case wxT( '"' ):
          strResult += wxT( '"' );
          break;
      }
    } else {
      if( str[n] != wxT( '"' ) || !bQuoted ) {
        strResult += str[n];
      } else if( n != str.Len() - 1 ) {
        wxLogWarning( _( "unexpected \" at position %d in '%s'." ), n, str.c_str() );
      }
    }
  }
  return strResult;
}

static wxString FilterOutValue( const wxString& str ) {
  if( !str ) {
    return str;
  }
  wxString strResult;
  strResult.Alloc( str.Len() );
  bool bQuote = wxIsspace( str[0] ) || str[0] == wxT( '"' );
  if( bQuote ) {
    strResult += wxT( '"' );
  }
  wxChar c;
  for( size_t n = 0; n < str.Len(); n++ ) {
    switch( str[n] ) {
      case wxT( '\n' ):
        c = wxT( 'n' );
        break;
      case wxT( '\r' ):
        c = wxT( 'r' );
        break;
      case wxT( '\t' ):
        c = wxT( 't' );
        break;
      case wxT( '\\' ):
        c = wxT( '\\' );
        break;
      case wxT( '"' ):
        if( bQuote ) {
          c = wxT( '"' );
          break;
        }
      default:
        strResult += str[n];
        continue;   // nothing special to do
    }
    strResult << wxT( '\\' ) << c;
  }
  if( bQuote ) {
    strResult += wxT( '"' );
  }
  return strResult;
}

static wxString FilterInEntryName( const wxString& str ) {
  wxString strResult;
  strResult.Alloc( str.Len() );
  for( const wxChar *pc = str.c_str(); *pc != '\0'; pc++ ) {
    if( *pc == wxT( '\\' ) ) {
      if( *++pc == _T( '\0' ) ) {
        break;
      }
    }
    strResult += *pc;
  }
  return strResult;
}

static wxString FilterOutEntryName( const wxString& str ) {
  wxString strResult;
  strResult.Alloc( str.Len() );
  for( const wxChar *pc = str.c_str(); *pc != wxT( '\0' ); pc++ ) {
    const wxChar c = *pc;
    if( !wxIsalnum( c ) && !wxStrchr( wxT( "@_/-!.*%" ), c ) ) {
      strResult += wxT( '\\' );
    }
    strResult += c;
  }
  return strResult;
}

static wxString GetAppName( const wxString& appName ) {
  if( !appName && wxTheApp ) {
    return wxTheApp->GetAppName();
  } else {
    return appName;
  }
}

#endif
